package cn.zmlio.mybatisdbchecker.scanner;

import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

@Slf4j
public class WarMapperScanner {
    // 扫描的标签类型
    private static final List<String> SUB_TAGS = Arrays.asList("insert", "update", "delete", "select");

    // 所有的命名空间
    @Getter
    private final List<String> namespaces = new LinkedList<>();
    @Getter
    private List<String> mappers = new LinkedList<>();

    @Getter
    private Set<String> statementIdsInXml = new LinkedHashSet<>();

    @Getter
    List<String> classFiles = new ArrayList<>();
    @Getter
    List<String> jarFiles = new ArrayList<>();
    @Getter
    Set<String> types = new HashSet<>();
    @Getter
    Map<String, String> parameterTypeMap = new HashMap<>();
    private final String warDir;

    public WarMapperScanner(String warDir) {
        this.warDir = warDir;
    }
    
    @SneakyThrows
    public void scan() {
        String classDir = getClassDir();
        String libDir = getLibDir();
        scanClassFile(new File(classDir));
        scanLib(new File(libDir));
    }

    public String getClassDir() {
        return warDir + "/WEB-INF/classes";
    }

    public String getLibDir() {
        return warDir + "/WEB-INF/lib";
    }

    private void scanLib(File lib) {
        if (lib.isDirectory()) {
            File[] files = lib.listFiles();
            if (files == null) {
                return;
            }
            for (File file1 : files) {
                scanLib(file1);
            }
        } else {

            if (lib.getName().endsWith(".jar")) {
                jarFiles.add(lib.getAbsolutePath());
                log.info("scanned jar file:{}", lib.getAbsolutePath());
                try (ZipFile zipFile = new ZipFile(lib)) {
                    Enumeration<? extends ZipEntry> entries = zipFile.entries();
                    while (entries.hasMoreElements()) {
                        ZipEntry entry = entries.nextElement();
                        if (entry.getName().endsWith(".xml")) {
                            log.info("scanning entry:{}", entry.getName());
                            readMapper(zipFile.getInputStream(entry), entry.getName());
                        }
                    }
                } catch (IOException | DocumentException e) {
                    log.error(e.getMessage(), e);
                }
            }
        }
    }

    private void scanClassFile(File file) throws DocumentException, IOException {
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            if (files == null) {
                return;
            }
            for (File file1 : files) {
                scanClassFile(file1);
            }
        } else {
            if (file.getName().endsWith(".class")) {
                classFiles.add(file.getAbsolutePath());
            }
            if (file.getName().endsWith(".xml")) {
                log.info("scanned class file:{}", file.getAbsolutePath());
                readMapper(Files.newInputStream(file.toPath()), file.getAbsolutePath());
            }
        }
    }

    private void readMapper(InputStream inputStream, String fileName) throws IOException, DocumentException {

        SAXReader saxReader = new SAXReader();
        Document document = saxReader.read(inputStream);
        Element rootElement = document.getRootElement();

        if (rootElement != null && "mapper".equals(rootElement.getName())) {

            log.info("scanned mapper xml file:{}", fileName);

            if (rootElement.attribute("namespace") != null) {
                mappers.add(fileName);
                String namespace = rootElement.attribute("namespace").getValue();
                log.info("scanned namespace :{}", namespace);
                namespaces.add(namespace);

                List<Element> elements = rootElement.elements();

                for (Element element : elements) {
                    if (!SUB_TAGS.contains(element.getName().toLowerCase())) {
                        continue;
                    }
                    if (element.attribute("id") == null) {
                        continue;
                    }
                    String id = element.attribute("id").getValue();
                    Attribute parameterType = element.attribute("parameterType");

                    if (parameterType != null) {
                        types.add(parameterType.getValue());
                        parameterTypeMap.put(namespace + "." + id, parameterType.getValue());
                    }

                    Attribute resultType = element.attribute("resultType");

                    if (resultType != null) {
                        types.add(resultType.getValue());
                    }
                    log.info("scanned method [{}] for namespace :{}", id, namespace);
                    statementIdsInXml.add(namespace + "." + id);
                }

            }
        }
    }
}